# vim: set fileencoding=utf-8 :

from django.core.files.uploadedfile import SimpleUploadedFile
from django.test import override_settings, TestCase
from django.urls import reverse
from django.utils.encoding import smart_str
from helpdesk import lib, models
import os
import shutil
from tempfile import gettempdir
from unittest import mock
from unittest.case import skip
from django.contrib.auth import get_user_model


MEDIA_DIR = os.path.join(gettempdir(), "helpdesk_test_media")


@override_settings(MEDIA_ROOT=MEDIA_DIR)
class AttachmentIntegrationTests(TestCase):
    fixtures = ["emailtemplate.json"]

    def setUp(self):
        self.queue_public = models.Queue.objects.create(
            title="Public Queue",
            slug="pub_q",
            allow_public_submission=True,
            new_ticket_cc="new.public@example.com",
            updated_ticket_cc="update.public@example.com",
        )

        self.queue_private = models.Queue.objects.create(
            title="Private Queue",
            slug="priv_q",
            allow_public_submission=False,
            new_ticket_cc="new.private@example.com",
            updated_ticket_cc="update.private@example.com",
        )

        self.ticket_data = {
            "title": "Test Ticket Title",
            "body": "Test Ticket Desc",
            "priority": 3,
            "submitter_email": "submitter@example.com",
        }

    def test_create_pub_ticket_with_attachment(self):
        test_file = SimpleUploadedFile(
            "test_att.txt", b"attached file content", "text/plain"
        )
        post_data = self.ticket_data.copy()
        post_data.update(
            {
                "queue": self.queue_public.id,
                "attachment": test_file,
            }
        )

        # Ensure ticket form submits with attachment successfully
        response = self.client.post(reverse("helpdesk:home"), post_data, follow=True)
        self.assertContains(response, test_file.name)

        # Ensure attachment is available with correct content
        att = models.FollowUpAttachment.objects.get(
            followup__ticket=response.context["ticket"]
        )
        with open(os.path.join(MEDIA_DIR, att.file.name)) as file_on_disk:
            disk_content = file_on_disk.read()
        self.assertEqual(disk_content, "attached file content")

    def test_create_pub_ticket_with_attachment_utf8(self):
        test_file = SimpleUploadedFile("ß°äöü.txt", "โจ".encode("utf-8"), "text/utf-8")
        post_data = self.ticket_data.copy()
        post_data.update(
            {
                "queue": self.queue_public.id,
                "attachment": test_file,
            }
        )

        # Ensure ticket form submits with attachment successfully
        response = self.client.post(reverse("helpdesk:home"), post_data, follow=True)
        self.assertContains(response, test_file.name)

        # Ensure attachment is available with correct content
        att = models.FollowUpAttachment.objects.get(
            followup__ticket=response.context["ticket"]
        )
        with open(
            os.path.join(MEDIA_DIR, att.file.name), encoding="utf-8"
        ) as file_on_disk:
            disk_content = smart_str(file_on_disk.read(), "utf-8")
        self.assertEqual(disk_content, "โจ")


@override_settings(MEDIA_ROOT=MEDIA_DIR)
class AttachmentIntegrationStaffTests(TestCase):
    def setUp(self):
        self.ticket = models.Ticket.objects.create(
            queue=models.Queue.objects.create(),
            title="Test attachments via ticket update",
        )
        self.default_update_post_data = {
            "queue": self.ticket.queue_id,
            "title": self.ticket.title,
            "priority": self.ticket.priority,
        }

    def loginUser(self, is_staff=True):
        """Create a staff user and login"""
        User = get_user_model()
        self.user = User.objects.create(
            username="User_1",
            is_staff=is_staff,
        )
        self.user.set_password("pass")
        self.user.save()
        self.client.login(username="User_1", password="pass")

    def test_update_ticket_with_attachment_valid_extension(self):
        self.loginUser(is_staff=True)
        file_content = "staff attached file content"
        test_file = SimpleUploadedFile(
            "test_staff_att.txt", bytes(file_content, "utf-8"), "text/plain"
        )
        post_data = {
            "attachment": test_file,
            **self.default_update_post_data,
        }
        # Ensure ticket form submits with attachment successfully
        self.client.post(
            reverse(
                "helpdesk:update",
                kwargs={"ticket_id": self.ticket.id},
            ),
            post_data,
            follow=True,
        )
        # Ensure attachment is available with correct content
        att = models.FollowUpAttachment.objects.get(followup__ticket=self.ticket)
        with open(os.path.join(MEDIA_DIR, att.file.name)) as file_on_disk:
            disk_content = file_on_disk.read()
        self.assertEqual(disk_content, file_content)

    def test_update_ticket_with_attachment_invalid_extension(self):
        self.loginUser(is_staff=True)
        file_content = "staff attached file content with invalid extension"
        file_extension = ".crash"
        test_file = SimpleUploadedFile(
            f"test_staff_att{file_extension}",
            bytes(file_content, "utf-8"),
            "text/plain",
        )
        post_data = {
            "attachment": test_file,
            **self.default_update_post_data,
        }
        # Ensure ticket form submits with attachment successfully
        response = self.client.post(
            reverse(
                "helpdesk:update",
                kwargs={"ticket_id": self.ticket.id},
            ),
            post_data,
            follow=True,
        )
        error_msg = response.context_data["form"].errors["attachment"][0]
        self.assertTrue(
            file_extension in error_msg,
            "Response indicates there were no errors attaching illegal file extension",
        )
        # Ensure attachment is not uploaded
        has_att = models.FollowUpAttachment.objects.filter(
            followup__ticket=self.ticket
        ).exists()
        self.assertFalse(has_att, "File was attached with invalid extension")


@mock.patch.object(models.FollowUp, "save", autospec=True)
@mock.patch.object(models.FollowUpAttachment, "save", autospec=True)
@mock.patch.object(models.Ticket, "save", autospec=True)
@mock.patch.object(models.Queue, "save", autospec=True)
class AttachmentUnitTests(TestCase):
    def setUp(self):
        self.file_attrs = {
            "filename": "°ßäöü.txt",
            "content": "โจ".encode("utf-8"),
            "content-type": "text/utf8",
        }
        self.test_file = SimpleUploadedFile.from_dict(self.file_attrs)
        self.follow_up = models.FollowUp.objects.create(
            ticket=models.Ticket.objects.create(queue=models.Queue.objects.create())
        )

    @skip("Rework with model relocation")
    def test_unicode_attachment_filename(
        self, mock_att_save, mock_queue_save, mock_ticket_save, mock_follow_up_save
    ):
        """check utf-8 data is parsed correctly"""
        filename, fileobj = lib.process_attachments(self.follow_up, [self.test_file])[0]
        mock_att_save.assert_called_with(
            file=self.test_file,
            filename=self.file_attrs["filename"],
            mime_type=self.file_attrs["content-type"],
            size=len(self.file_attrs["content"]),
            followup=self.follow_up,
        )
        self.assertEqual(filename, self.file_attrs["filename"])

    def test_autofill(
        self, mock_att_save, mock_queue_save, mock_ticket_save, mock_follow_up_save
    ):
        """check utf-8 data is parsed correctly"""
        obj = models.FollowUpAttachment.objects.create(
            followup=self.follow_up, file=self.test_file
        )
        obj.save()
        self.assertEqual(obj.file.name, self.file_attrs["filename"])
        self.assertEqual(obj.file.size, len(self.file_attrs["content"]))
        self.assertEqual(obj.file.file.content_type, "text/utf8")

    def test_kbi_attachment(
        self, mock_att_save, mock_queue_save, mock_ticket_save, mock_follow_up_save
    ):
        """check utf-8 data is parsed correctly"""

        kbcategory = models.KBCategory.objects.create(
            title="Title", slug="slug", description="Description"
        )
        kbitem = models.KBItem.objects.create(
            category=kbcategory, title="Title", question="Question", answer="Answer"
        )

        obj = models.KBIAttachment.objects.create(kbitem=kbitem, file=self.test_file)
        obj.save()
        self.assertEqual(obj.filename, self.file_attrs["filename"])
        self.assertEqual(obj.file.size, len(self.file_attrs["content"]))
        self.assertEqual(obj.mime_type, "text/plain")

    @skip("model in lib not patched")
    @override_settings(MEDIA_ROOT=MEDIA_DIR)
    def test_unicode_filename_to_filesystem(
        self, mock_att_save, mock_queue_save, mock_ticket_save, mock_follow_up_save
    ):
        """don't mock saving to filesystem to test file renames caused by storage layer"""
        filename, fileobj = lib.process_attachments(self.follow_up, [self.test_file])[0]
        # Attachment object was zeroth positional arg (i.e. self) of att.save
        # call
        attachment_obj = mock_att_save.return_value

        mock_att_save.assert_called_once_with(attachment_obj)
        self.assertIsInstance(attachment_obj, models.FollowUpAttachment)
        self.assertEqual(attachment_obj.filename, self.file_attrs["filename"])


def tearDownModule():
    try:
        shutil.rmtree(MEDIA_DIR)
    except OSError:
        pass
